package br.edu.ufcg.splab.simulator.jgrapht;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import br.edu.ufcg.splab.generator.TestCase;
import br.edu.ufcg.splab.generator.TestSuite;
import br.edu.ufcg.splab.techniques.reduction.functions.similarity.DistanceFunction;
import br.edu.ufcg.splab.techniques.reduction.strategies.SimilarityMatrix;

public class AssignerDefects {

	private TestSuite testSuite;
	private DistanceFunction function;
	private double percentageMinimum;
	
	/**
	 * Returns the relationship between defects and failures in a test suite from the maximal cliques.
	 * @param testSuite The test suite in which will be assigned defects.
	 * @param function The distance function to define the relationship between two test cases.
	 * @param percentageMinimum The percentage minimum relative the maximum value in the matrix to define a strong relation between two test cases.
	 * @param percentageOfDefects The percentage of defects to be selected from the test suite.
	 * @return A set of defects and failures from the test suite.
	 */
	public Map<Integer, List<TestCase>> assignDefects(TestSuite testSuite, DistanceFunction function, double minimumValue, double percentageOfDefects, InterfaceCliqueFinder maximumClique) {
		this.testSuite = testSuite;
		this.function = function;
		Collection<Set<TestCase>> allCliques = maximumClique.getAllMaximalCliques(testSuite, function, minimumValue);
		int numberOfDefects = (int) Math.round(allCliques.size() * (percentageOfDefects/100.00)); 	
		return defineDefects(allCliques, numberOfDefects);
	}
	
	/**
	 * Returns the relationship between defects and failures in a test suite from the maximal cliques.
	 * @param testSuite The test suite in which will be assigned defects.
	 * @param function The distance function to define the relationship between two test cases.
	 * @param percentageMinimum The percentage minimum relative the maximum value in the matrix to define a strong relation between two test cases.
	 * @param numberOfFailures The number of failures to be selected from the test suite.
	 * @return A set of defects and failures from the test suite.
	 */
	public Map<Integer, List<TestCase>> assignDefects(TestSuite testSuite, DistanceFunction function, double percentageMinimum, int numberOfFailures, InterfaceCliqueFinder maximumClique) {
		this.testSuite = testSuite;
		this.function = function;
		this.percentageMinimum = percentageMinimum;
		
		double minimumValue = getMinimumValue();	
		
		Set<TestCase> testCases = chooseRandomTestCases(numberOfFailures);
		TestSuite failTS = new TestSuite();
		failTS.addAll(testCases);
		
		Collection<Set<TestCase>> allCliques = maximumClique.getAllMaximalCliques(failTS, function, minimumValue);
		return getMapDefects(allCliques);
	}
	
	/**
	 * Returns the relationship between defects and failures in a test suite from the all cliques.
	 * @param testSuite The test suite in which will be assigned defects.
	 * @param function The distance function to define the relationship between two test cases.
	 * @param percentageMinimum The percentage minimum relative the maximum value in the matrix to define a strong relation between two test cases.
	 * @param percentageOfDefects The percentage of defects to be selected from the test suite.
	 * @return A set of defects and failures from the test suite.
	 */
	public Map<Integer, List<TestCase>> assignDefectsAllCliques(TestSuite testSuite, DistanceFunction function, double percentageMinimum, double percentageOfDefects, InterfaceCliqueFinder maximumClique) {
		this.testSuite = testSuite;
		this.function = function;
		this.percentageMinimum = percentageMinimum;
		
		double minimumValue = getMinimumValue();	
		Collection<Set<TestCase>> allCliques = maximumClique.getAllCliques(testSuite, function, minimumValue);
		int numberOfDefects = (int) Math.round(allCliques.size() * (percentageOfDefects/100.00)); 	
		return defineDefects(allCliques, numberOfDefects);
	}
	
	/**
	 * Returns the relationship between defects and failures in a test suite from the all cliques.
	 * @param testSuite The test suite in which will be assigned defects.
	 * @param function The distance function to define the relationship between two test cases.
	 * @param percentageMinimum The percentage minimum relative the maximum value in the matrix to define a strong relation between two test cases.
	 * @param numberOfFailures The number of failures to be selected from the test suite.
	 * @return A set of defects and failures from the test suite.
	 */
	public Map<Integer, List<TestCase>> assignDefectsAllCliques(TestSuite testSuite, DistanceFunction function, double percentageMinimum, int numberOfFailures, InterfaceCliqueFinder maximumClique) {
		this.testSuite = testSuite;
		this.function = function;
		this.percentageMinimum = percentageMinimum;
		
		double minimumValue = getMinimumValue();	
		
		Set<TestCase> testCases = chooseRandomTestCases(numberOfFailures);
		TestSuite failTS = new TestSuite();
		failTS.addAll(testCases);
		
		Collection<Set<TestCase>> allCliques = maximumClique.getAllCliques(failTS, function, minimumValue);
		return getMapDefects(allCliques);
	}
	
	/**
	 * Returns the relationship between defects and failures in a test suite.
	 * @param testSuite The test suite in which will be assigned defects.
	 * @param function The distance function to define the relationship between two test cases.
	 * @param percentageMinimum The percentage minimum relative the maximum value in the matrix to define a strong relation between two test cases.
	 * @param percentageOfDefects The percentage of defects to be selected from the test suite.
	 * @return A set of defects and failures from the test suite.
	 */
	public Map<Integer, List<TestCase>> assignDefects(TestSuite testSuite, DistanceFunction function, double percentageMinimum, Set<TestCase> fail, InterfaceCliqueFinder maximumClique) {
		this.testSuite = testSuite;
		this.function = function;
		this.percentageMinimum = percentageMinimum;
		
		double minimumValue = getMinimumValue();	
		
		TestSuite failTS = new TestSuite();
		failTS.addAll(fail);
		Collection<Set<TestCase>> allCliques = maximumClique.getAllCliques(failTS, function, minimumValue);

		Map<Integer,List<TestCase>> mapDefects = getMapDefects(allCliques);
		return mapDefects;	
	}
	
	private Map<Integer,List<TestCase>> getMapDefects(Collection<Set<TestCase>> allCliques){
		Map<Integer,List<TestCase>> mapDefects = new HashMap<Integer, List<TestCase>>();
		
		int countFault = 1;
		for(Set<TestCase> set:allCliques){
			mapDefects.put(countFault, new ArrayList<TestCase>(set));
			countFault++;
		}	    
		return mapDefects;	
	}
	
	/*
	 * Sets the minimum value for a strong relation between two test cases on matrix from of a distance function.
	 * @return The minimum value to define a strong relationship between two test cases.
	 */
	private double getMinimumValue(){ 
		SimilarityMatrix matrix = new SimilarityMatrix();
		double maximumValue = matrix.getMaximumValue(testSuite, function);
		return maximumValue * percentageMinimum;
	}
	
		
	/*
	 * This method defines the defects among a set of test cases that fail.
	 * @param testCases The test cases that fail.
	 * @param minimumValue The minimum value to define a strong relationship between two test cases.
	 * @return The defects among a set of test cases that fail.
	 */
	private Map<Integer, List<TestCase>> defineDefects(Collection<Set<TestCase>> allCliques, int numberOfDefects){		
		//identification of each defect
		Map<Integer,List<TestCase>> mapDefects = new HashMap<Integer, List<TestCase>>();
		
		Collection<Set<TestCase>> defects = chooseRandomCliques(allCliques, numberOfDefects);
		
		int countFault = 1;
		for(Set<TestCase> set:defects){
			mapDefects.put(countFault, new ArrayList<TestCase>(set));
			countFault++;
		}	    
		return mapDefects;		
	}
	
	/*
	 * Returns a set of test cases selected randomly.
	 * @return A set of test cases selected randomly.
	 */
	private Collection<Set<TestCase>> chooseRandomCliques(Collection<Set<TestCase>> cliques, int numberOfDefects){			
		List<Set<TestCase>> list = new ArrayList<Set<TestCase>>(cliques);
	    Collections.shuffle(list);
	    Set<Set<TestCase>> defects = new HashSet<Set<TestCase>>(list.subList(0, numberOfDefects));	    
	    
		return defects;
	}
	
	
	/*
	 * Returns a set of test cases selected randomly.
	 * @return A set of test cases selected randomly.
	 */
	private Set<TestCase> chooseRandomTestCases(int numberOfFailures){
		List<TestCase> testCases = testSuite.getTestSuite();
		Set<TestCase> failures = new HashSet<TestCase>();
		for (int i = 0; i < numberOfFailures; i++) {
			if(!testCases.isEmpty()){
				if(!testCases.isEmpty()){
					TestCase randomTC = testCases.get(new Random().nextInt(testCases.size()));
					failures.add(randomTC);
					testCases.remove(randomTC);
				}
			}
		}
		return failures;
	}

}